// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// This class contains range values and functions to manipulate them. Ranges are
// a list of segments such that each segment have a start and end value and
// segments do not overlap.
// Hue range values are in the range [0, kMaxHue) which is set to [0, 2*pi).
// Segments are treated as circular (i.e. [6, 1] is not empty, it contains
// [6, 2*pi) and [0, 1]).

#ifndef HUE_RANGE_H_
#define HUE_RANGE_H_

#include <math.h>
#include <vector>
#include "compat.h"
#include "image.h"
#include "array2d.h"
#include "max_flow.h"

namespace harmonizer {

class HueRange {
 public:
  // selector for the method we would like to use when doing hue adjustments
  enum HueFitMethod {
      CLOSEST_VALID_VALUE,
      LINEAR_SCALE,
      GAUSSIAN_SCALE,
      LINEAR_SCALE_MINCUT,
      HUE_FIT_METHOD_NUM_ENUMERATION_VALUES
  };

  // selector for the method we use to scale values into a segment
  enum SegmentScaleMethod {
      BOTH_SIDES,
      PULL_VALUES_BACKWARD,
      PUSH_VALUES_FORWARD,
      SEGMENT_SCALE_METHOD_NUM_ENUMERATION_VALUES
  };

  // Segment() represents a single range of values
  class Segment {
   public:
    Segment(float input_from, float input_to);
    bool IsWithinSegment(float value) const;
    bool IsStrictlyWithinSegment(float value) const;
    bool IsWithinSegmentOrBeforeOther(float value, Segment other) const;
    bool IsOverlappingSegment(Segment other) const;
    float FindClosestValueInSegment(float value,
                                    SegmentScaleMethod scale_method) const;
    float FindLinearScaledValueInSegment(float value,
                                         float maximal_distance,
                                         SegmentScaleMethod scale_method) const;
    float FindGaussianScaledValueInSegment(
        float value, float maximal_distance,
        SegmentScaleMethod scale_method) const;
    float DistanceFromEdge(float value) const;
    float DistanceFromCenter(float value) const;
    float SignedDistanceFromCenter(float value) const;
    float DistanceOneSidedAfterStart(float value) const;
    float DistanceOneSidedBeforeEnd(float value) const;
    float Center() const;
    float Width() const;

    float from_;
    float to_;

    // helper functions:

    // Returns N(x) such that N() is the Normal distribution with mu = 0 and
    // the given sigma.
    static double Gaussian(double x, double sigma);
    // Same as Gaussian() + values are scaled to be in [0, 1] (for x in
    // [-kMaxHue/2, kMaxHue/2], which are the valid distance values).
    static double NormalizedGaussian(double x, double sigma,
                                     float maximal_distance);
  };

  // Constructors and destructor. We construct by either supplying a single
  // segment or a list of segments.
  HueRange(float from, float to);
  explicit HueRange(const std::vector<float>& vector_of_ranges);
  virtual ~HueRange();

  // Reset the ranges to new hue values
  void Reset(float from, float to);
  bool Reset(const std::vector<float>& vector_of_ranges);

  // Range queries
  bool IsValueWithinRange(float hue_value) const;
  bool IsOverlappingRange(float from, float to) const;
  bool IsOverlappingRange(Segment segment) const;
  void FitToRange(HueFitMethod fit_method,
                  FloatImage* hsv_image) const;

  // Info regarding the HueRange
  int NumberOfSegments() const;

  // Labeling pixels
  void AssignToSegmentUsingClosest(
      const FloatImage& hsv_image,
      Array2D<uint8>* assignment) const;
  void AssignToSegmentUsingMincut(const FloatImage& hsv_image, float lambda,
                                  Array2D<uint8>* assignment) const;

  // define range limit
  static constexpr float kMaxHue = 2 * M_PI;

  // For each image pixel, calculate which 2 segments it belongs to. Here
  // "belongs" refers to a hue value being in or between two segments, thus
  // those two segments are the two possible destinations for that hue value.
  // We return an index value to represent the 2 segments. For example, if our
  // segment list is [1,2], [5,0] then we will have the following labeling:
  // 0 - all hues in [1,5)
  // 1 - all hues in [5,1)
  // Another example: for [3,4], [5,6], [1,2] we'll have the labeling:
  // 0 - all hues in [3,5)
  // 1 - all hues in [5,1)
  // 2 - all hues in [1,3)
  void FitHuesToRanges(const FloatImage& hsv_image,
      Array2D<uint8>* range_labels) const;

 private:
  // We need a slightly different algorithm for the case where we only fit into
  // a single segment. In this case we split the segment into 2 equal parts, so
  // that the mapping can choose between mapping into the segment in the forward
  // or in the reverse direction.
  // We achieve this by creating a new HueRange() with split_occured = true. Not
  // that this ctor is private and can only be called internally. The
  // split_occured_ flag is checked in the different fit methods, and behavior
  // is tweaked accordingly.
  HueRange(float from, float to, bool split_occurred);
  HueRange(const std::vector<float>& vector_of_ranges, bool split_occurred);

  // Distance calculations
  static float CircularDistance(float hue1, float hue2);
  static float CircularDistanceWithMinValue(float hue1, float hue2,
                                            float min_value);
  int GetClosestSegmentIndices(float hue_value) const;

  // Given a list of linear indices and image dimension, we return a 2D array
  // with true for each index in the list and false for all indices not in the
  // list.
  static void LinearIndicesToBooleanArray(
      int width, int height,
      const vector<max_flow::NodeIndex>& indices,
      Array2D<bool>* bool_array);

  // Given two specific segments (which might be the same one), produce a
  // labeling.
  // 'lambda' is used for the mincut labeling method. This parameter determines
  // the relative weight of pixel gradient vs. distance from target hues in the
  // optimization.
  static void LabelImageTwoSegments(
      Segment seg1, Segment seg2,
      const FloatImage& hsv_image, float lambda,
      Array2D<bool>* labels);

  // Holds the list of segments which comprise the HueRange.
  std::vector<Segment> hue_segments_;

  // For the special case of a single segment, we split it into two segments. If
  // this special case occurred, we indicate it using this flag.
  const bool split_occurred_;

  // Disallow copy and assignment.
  HueRange(const HueRange& toCopy);
  HueRange& operator=(const HueRange& toCopy);
};

}  // namespace harmonizer

#endif  // HUE_RANGE_H_
